﻿using System.Linq.Expressions;
using Microsoft.AspNetCore.Http;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;

namespace Devonline.Database.MongoDB;

/// <summary>
/// MongoDB 数据服务得默认实现
/// </summary>
public sealed class MongoDataService : MongoService, IMongoDataService, IMongoService, INoSQLDataService, INoSQLService
{
    /// <summary>
    /// Http Context
    /// </summary>
    private readonly HttpContext _httpContext;
    /// <summary>
    /// Http Request
    /// </summary>
    private readonly HttpRequest _request;
    /// <summary>
    /// Http 配置
    /// </summary>
    private readonly HttpSetting _httpSetting;

    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="endpoint"></param>
    /// <param name="httpContextAccessor"></param>
    /// <param name="httpSetting"></param>
    public MongoDataService(ILogger<MongoDataService> logger, IMongoEndpoint endpoint, IHttpContextAccessor httpContextAccessor, HttpSetting httpSetting) : base(logger, endpoint)
    {
        _httpContext = httpContextAccessor.HttpContext!;
        _request = httpContextAccessor.HttpContext!.Request;
        _httpSetting = httpSetting;
        UserName = GetUserName();
        DataIsolate = httpSetting.DataIsolate;
        DataIsolateId = GetDataIsolateId();
    }

    /// <summary>
    /// 当前登录的用户编号
    /// </summary>
    public string UserId => GetUserId() ?? throw new UnauthorizedAccessException();
    /// <summary>
    /// 当前登录的用户
    /// </summary>
    public string UserName { get; }
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识在 User.Claims 中的 type 为 sub; 在 request 中为 userId
    /// 用户尚未登录系统时抛出 UnauthorizedAccessException 异常
    /// </summary>
    /// <returns></returns>
    /// <exception cref="UnauthorizedAccessException">用户尚未认证访问异常</exception>
    public string? GetUserId() => _httpContext.GetUserId<string>();
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识在 User.Claims 中的 type 为 userName
    /// </summary>
    /// <returns></returns>
    public string GetUserName() => _httpContext.GetUserName();
    /// <summary>
    /// 获取数据隔离的数据编号
    /// </summary>
    /// <returns></returns>
    public string? GetDataIsolateId() => _httpContext.GetClaimValue<string>(AppSettings.CLAIM_TYPE_DATA_ISOLATE_ID);

    /// <summary>
    /// 根据过滤条件返回查询结果list
    /// </summary>
    /// <typeparam name="TModel">数据对象模型的类型</typeparam>
    /// <returns></returns>
    public override async Task<IQueryable<TModel>> GetQueryableAsync<TModel>()
    {
        var typeName = typeof(TModel).GetDisplayName();
        _logger.LogDebug("用户 {userName} 将查询 {host} 的数据库 {database}", _httpContext.GetUserName(), _endpoint.Host, _endpoint.Database);
        var collection = await GetOrCreateCollectionAsync<TModel>();
        var queryable = collection.AsQueryable();

        _logger.LogDebug($"The user {UserName} query {typeName} from cache");
        if (DataIsolate != DataIsolateLevel.None && DataIsolateId is not null && queryable is IQueryable<IIsolate>)
        {
            _logger.LogDebug($"The user {UserName} query {typeName} from cache in data isolate: {DataIsolateId}");
            queryable = queryable.Where(x => ((IIsolate)x).Isolate.Equals(DataIsolateId));
        }

        return queryable;
    }

    /// <summary>
    /// 从数据库查询多行数据
    /// </summary>
    /// <typeparam name="TModel">数据对象模型的类型</typeparam>
    /// <returns></returns>
    public async Task<PagedResult<TModel>> GetPagedResultAsync<TModel>() where TModel : class, new() => await _request.GetPagedResultAsync<TModel>(await GetQueryableAsync<TModel>());
    /// <summary>
    /// 从数据库查询多行数据
    /// </summary>
    /// <typeparam name="TModel">数据对象模型的类型</typeparam>
    /// <param name="predicate">查询表达式</param>
    /// <returns></returns>
    public async Task<PagedResult<TModel>> GetPagedResultAsync<TModel>(Expression<Func<TModel, bool>> predicate) where TModel : class, new() => await _request.GetPagedResultAsync<TModel>(await GetQueryableAsync<TModel>(predicate));

    /// <summary>
    /// 调用实例的 Create 方法为基础字段赋值
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    public override void Create<TModel>(TModel model)
    {
        base.Create<TModel>(model);
        if (_httpSetting.DataIsolate != DataIsolateLevel.None && DataIsolateId is not null && model is IIsolate dataIsolate)
        {
            dataIsolate.Isolate = DataIsolateId;
        }
    }

    /// <summary>
    /// 写入一行数据到数据库
    /// </summary>
    /// <typeparam name="TModel">数据对象模型的类型</typeparam>
    /// <param name="model">待写入的数据</param>
    /// <returns></returns>
    protected override async Task InternalAddAsync<TModel>(TModel model)
    {
        //await UniqueAsync(model);
        Create(model);
        Update(model);
        await (await GetOrCreateCollectionAsync<TModel>()).InsertOneAsync(model);
    }
    /// <summary>
    /// 写入多行数据到数据库
    /// </summary>
    /// <typeparam name="TModel">数据对象模型的类型</typeparam>
    /// <param name="data">待写入的数据</param>
    /// <returns></returns>
    protected override async Task InternalAddsAsync<TModel>(IEnumerable<TModel> data)
    {
        var adds = new List<TModel>();
        foreach (var model in data)
        {
            //await UniqueAsync(model);
            Create(model);
            Update(model);
            adds.Add(model);
        }

        await (await GetOrCreateCollectionAsync<TModel>()).InsertManyAsync(adds);
    }
    /// <summary>
    /// 更新数据到数据库
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="model"></param>
    /// <returns></returns>
    protected override async Task InternalUpdateAsync<TModel>(TModel model) => await (await GetOrCreateCollectionAsync<TModel>()).UpdateOneAsync(x => x.Id == model.Id, BsonDocument.Create(model));
    /// <summary>
    /// 从数据库删除数据
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="data"></param>
    /// <returns></returns>
    protected override async Task InternalUpdatesAsync<TModel>(IEnumerable<TModel> data) => await (await GetOrCreateCollectionAsync<TModel>()).UpdateManyAsync(x => data.Select(a => a.Id).Contains(x.Id), BsonDocument.Create(data));
}